import 'package:community_charts_flutter/community_charts_flutter.dart'
    as charts;
import 'package:family_expense/features/income_expense/constants/income_expense_type.dart';
import 'package:family_expense/features/income_expense/constants/payment_type.dart';
import 'package:family_expense/features/income_expense/models/income_expense_detail.dart';
import 'package:family_expense/features/income_expense/widgets/income_expense_detail_shared.dart';
import 'package:family_expense/features/income_expense/widgets/income_expense_detail_view.dart';
import 'package:family_expense/features/income_expense_analysis/services/income_expense_analysis_service.dart';
import 'package:family_expense/features/income_expense_analysis/widgets/income_expense_analysis_shared.dart';
import 'package:family_expense/features/income_expense_detail_type/models/income_expense_detail_type.dart';
import 'package:family_expense/features/income_expense_detail_type/services/income_expense_detail_type_loader.dart';
import 'package:family_expense/features/income_expense_detail_type/services/income_expense_detail_type_service.dart';
import 'package:family_expense/features/income_expense_detail_type/widgets/income_expense_detail_type_home.dart';
import 'package:family_expense/features/income_expense_search/widgets/income_expense_search_result.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:my_flutter_core/my_flutter_core.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';

class IncomeExpenseAnnualAnalysis extends StatefulWidget {
  const IncomeExpenseAnnualAnalysis({super.key, this.year, this.yearRange})
    : assert(year != null || yearRange != null);

  final int? year;
  final String? yearRange;

  @override
  State<IncomeExpenseAnnualAnalysis> createState() =>
      _IncomeExpenseAnnualAnalysisState();
}

class _IncomeExpenseAnnualAnalysisState
    extends State<IncomeExpenseAnnualAnalysis>
    with SingleTickerProviderStateMixin {
  final int _pageSize = GeneralConfig.listViewPageSize;
  final int _overviewSummaryTabIndex = 0;
  final int _comparisonChartTabIndex = 1;
  final int _expenseSummaryByDetailTypeTabIndex = 2;
  final int _incomeSummaryByDetailTypeTabIndex = 3;
  final int _expenseTopListIndex = 4;
  final int _incomeTopListIndex = 5;

  // For getting analysis not included detail types.
  final IncomeExpenseDetailTypeService _incomeExpenseDetailTypeService =
      IncomeExpenseDetailTypeService();

  // For getting all analysis data.
  final IncomeExpenseAnalysisService _incomeExpenseAnalysisService =
      IncomeExpenseAnalysisService();

  late final TabController _tabController = TabController(
    vsync: this,
    length: 6,
  );

  // With TabView & TabController when tapping between tabs in the AppBar there are two renderings
  // (i.e. the listener function is called twice, not once, thus generating two rebuilds), but when swiping there is only one rebuild.
  // LoadingStatus is used to void such duplicate loading.
  LoadingStatus _overviewSummaryLoadingStatus = LoadingStatus.notLoaded;

  LoadingStatus _analysisNotIncludedDetailTypesLoadingStatus =
      LoadingStatus.notLoaded;

  LoadingStatus _comparisonDataLoadingStatus = LoadingStatus.notLoaded;

  LoadingStatus _expenseSummaryByDetailTypeDataLoadingStatus =
      LoadingStatus.notLoaded;

  LoadingStatus _incomeSummaryByDetailTypeDataLoadingStatus =
      LoadingStatus.notLoaded;

  List<IncomeExpenseDetailType> _analysisNotIncludedIncomeDetailTypes = [];
  List<IncomeExpenseDetailType> _analysisNotIncludedExpenseDetailTypes = [];
  List<charts.Series> _incomeExpenseComparisonChartData = [];
  // List<charts.Series> _expenseSummaryByDetailTypeChartData = [];
  // List<charts.Series> _incomeSummaryByDetailTypeChartData = [];
  double _annualCashIncomeAmount = 0.0;
  double _annualCashExpenseAmount = 0.0;
  double _annualCreditCardIncomeAmount = 0.0;
  double _annualCreditCardExpenseAmount = 0.0;
  String _paymentType = '';
  bool _showPaymentSelectionButton = false;
  bool _dataUpdated = false;

  Map<String, dynamic>? _summaryDataCollection;
  int? _selectedMonth;
  User? _currentUser;

  // Used to reset the list once the income/expense detail updated.
  late PagingListViewLoadController _expenseTopListLoadController;
  late PagingListViewLoadController _incomeTopListLoadController;

  late List<Map<String, dynamic>> _expenseSummaryByDetailTypeData;
  late List<Map<String, dynamic>> _incomeSummaryByDetailTypeData;

  @override
  void initState() {
    super.initState();

    // Uses [animation.addListener] instead of [addListener] because [addListener] only supports tapping but not swapping gesture.
    // [animation.addListener] supports both.
    _tabController.animation!.addListener(_listenOnTabChange);

    _getCurrentUser();
    _loadAnalysisData();
    _loadAnalysisNotIncludedDetailTypes();
  }

  @override
  void dispose() {
    _tabController.removeListener(_listenOnTabChange);
    _tabController.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: _onPopInvokedWithResult,
      child: Scaffold(appBar: _buildAppBar(), body: _buildBody()),
    );
  }

  /// Builds app bar.
  PreferredSizeWidget _buildAppBar() {
    return AppBar(
      title: _buildAppTitile(),
      actions: <Widget>[
        _buildPaymentSelectionButton(),
        if (widget.year != null) _buildMonthSelectionButton(),
      ],
    );
  }

  /// Builds and shows the linear progress indicator when data is loading.
  Widget _buildLinearProgressIndicator() {
    if ((_tabController.index == _overviewSummaryTabIndex &&
            (_overviewSummaryLoadingStatus != LoadingStatus.loaded ||
                _analysisNotIncludedDetailTypesLoadingStatus !=
                    LoadingStatus.loaded)) ||
        (_tabController.index == _comparisonChartTabIndex &&
            _comparisonDataLoadingStatus != LoadingStatus.loaded) ||
        (_tabController.index == _expenseSummaryByDetailTypeTabIndex &&
            _expenseSummaryByDetailTypeDataLoadingStatus !=
                LoadingStatus.loaded) ||
        (_tabController.index == _incomeSummaryByDetailTypeTabIndex &&
            _incomeSummaryByDetailTypeDataLoadingStatus !=
                LoadingStatus.loaded)) {
      return const MyLinearProgressIndicator();
    }

    return const SizedBox(height: 2.0);
  }

  /// Builds the app titile to display the year, month and payment info.
  Widget _buildAppTitile() {
    if (widget.year == null) {
      return Text('${widget.yearRange} 收支分析');
    }

    return Text(
      '${widget.year}年${_selectedMonth == null ? '' : '$_selectedMonth月'}${PaymentType.getCategories()[_paymentType] ?? '全部'}收支分析',
    );
  }

  /// Builds the month selection button to change the month.
  Widget _buildMonthSelectionButton() {
    return TextButton(
      child: Text(
        _selectedMonth == null ? '全年' : '$_selectedMonth月',
        style: TextStyle(
          color: calculateContrastingColor(
            Theme.of(context).appBarTheme.backgroundColor!,
          ),
        ),
      ),
      onPressed: () {
        _showMonthModalBottomSheet((month) {
          // Why not call setState becuase _markForDataReloading will set state.
          _selectedMonth = month;

          // For displaying loading icon.
          _markForDataReloading(markAll: true);

          // Prepares the data based on current tab.
          _prepareDataBasedOnTab();

          // Resets the top list.
          _resetPagingListBasedOnTab();

          // Pops the modal.
          NavigatorHelper.pop(context);
        }, month: _selectedMonth);
      },
    );
  }

  /// Builds the payment selection button for selecting credit card/cash.
  /// This menu is for rank list only.
  Widget _buildPaymentSelectionButton() {
    return Visibility(
      visible: _showPaymentSelectionButton,
      child: PopupMenuButton<String>(
        tooltip: '显示支付方式',
        icon:
            IncomeExpenseDetailShared.getTypeIcon(_paymentType) ??
            Text(
              '全部',
              style: TextStyle(
                color: calculateContrastingColor(
                  Theme.of(context).appBarTheme.backgroundColor!,
                ),
              ),
            ),
        onSelected: (type) {
          // Why not call setState becuase _markForDataReloading will set state.
          _paymentType = type;

          // For displaying loading icon.
          _markForDataReloading(markAll: true);

          // Prepares the data based on current tab.
          _prepareDataBasedOnTab();

          // Resets the top list.
          _resetPagingListBasedOnTab();
        },
        itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
          PopupMenuItem<String>(
            value: '',
            child: _buildPaymentSelectionMenuItem(
              '',
              highlight: _paymentType == '',
            ),
          ),
          PopupMenuItem<String>(
            value: PaymentType.cash,
            child: _buildPaymentSelectionMenuItem(
              PaymentType.cash,
              highlight: _paymentType == PaymentType.cash,
            ),
          ),
          PopupMenuItem<String>(
            value: PaymentType.creditCard,
            child: _buildPaymentSelectionMenuItem(
              PaymentType.creditCard,
              highlight: _paymentType == PaymentType.creditCard,
            ),
          ),
        ],
      ),
    );
  }

  /// Builds the item for payment selection menu item.
  Widget _buildPaymentSelectionMenuItem(
    String paymentType, {
    bool highlight = false,
  }) {
    return Row(
      children: [
        paymentType.isNullOrEmpty
            ? Icon(
                MdiIcons.bellOutline,
                color: highlight ? Colors.blue : Colors.grey,
              )
            : IncomeExpenseDetailShared.getTypeIcon(
                paymentType,
                color: highlight ? Colors.blue : Colors.grey,
              )!,
        const SizedBox(width: 10.0),
        highlight
            ? Text(
                PaymentType.getCategories()[paymentType] ?? '全部',
                style: const TextStyle(color: Colors.blue),
              )
            : Text(PaymentType.getCategories()[paymentType] ?? '全部'),
      ],
    );
  }

  /// Builds the body.
  Widget _buildBody() {
    return Column(
      children: [
        _buildLinearProgressIndicator(),
        SizedBox(
          height: 30.0,
          child: TabBar(
            controller: _tabController,
            isScrollable: true,
            labelColor: Colors.black54,
            tabs: const <Widget>[
              Tab(text: '概况'),
              Tab(text: '对比'),
              Tab(text: '支出类别'),
              Tab(text: '收入类别'),
              Tab(text: '支出排行'),
              Tab(text: '收入排行'),
            ],
          ),
        ),
        Expanded(
          child: Padding(
            padding: const EdgeInsets.all(4.0),
            child: TabBarView(
              controller: _tabController,
              children: [
                _buildSummaryInfo(),
                _buildIncomeExpenseComparisonChart(),
                // _buildExpenseCategoryChart(),
                // _buildIncomeCategoryChart(),
                _buildSummaryByDetailTypeList(IncomeExpenseType.expense),
                _buildSummaryByDetailTypeList(IncomeExpenseType.income),
                _buildTopPagingList(IncomeExpenseType.expense),
                _buildTopPagingList(IncomeExpenseType.income),
              ],
            ),
          ),
        ),
      ],
    );
  }

  /// Calculates the total summary based on provided [cashSummary] and [creditCardSummary]. It convers the expense and income.
  Map<String, dynamic> _calculateTotalSummary(
    Map<String, dynamic> cashSummary,
    Map<String, dynamic>? creditCardSummary,
  ) {
    Map<String, dynamic> result = {};

    cashSummary.forEach((key, value) {
      result[key] = cashSummary[key] + creditCardSummary![key];
    });

    return result;
  }

  /// Builds overview summary info.
  Widget _buildSummaryInfo() {
    return RefreshIndicator(
      child: ListView(
        children: [
          _buildSummaryCard(PaymentType.cash),
          _buildSummaryCard(PaymentType.creditCard),
          _buildSummaryCard(null),
          _buildNotIncludedDetailTypeCard(),
        ],
      ),
      onRefresh: () async {
        if (mounted) {
          setState(() {
            _overviewSummaryLoadingStatus = LoadingStatus.notLoaded;
            _comparisonDataLoadingStatus = LoadingStatus.notLoaded;
          });

          _prepareDataBasedOnTab();
        }
      },
    );
  }

  /// Builds the card to display the summary info based on [paymentType].
  /// If the [paymentType] is null that means the total amount info.
  Widget _buildSummaryCard(String? paymentType) {
    Widget card;

    final annualCashBalanceAmount =
        _annualCashIncomeAmount + _annualCashExpenseAmount;

    final annualCreditCardBalanceAmount =
        _annualCreditCardIncomeAmount + _annualCreditCardExpenseAmount;

    switch (paymentType) {
      case PaymentType.cash:
        card = IncomeExpenseAnalysisSummaryCard(
          _annualCashIncomeAmount,
          _annualCashExpenseAmount,
          annualCashBalanceAmount,
          paymentType,
        );
        break;
      case PaymentType.creditCard:
        card = IncomeExpenseAnalysisSummaryCard(
          _annualCreditCardIncomeAmount,
          _annualCreditCardExpenseAmount,
          annualCreditCardBalanceAmount,
          paymentType,
        );
        break;
      default:
        card = IncomeExpenseAnalysisSummaryCard(
          _annualCashIncomeAmount + _annualCreditCardIncomeAmount,
          _annualCashExpenseAmount + _annualCreditCardExpenseAmount,
          annualCashBalanceAmount + annualCreditCardBalanceAmount,
          paymentType,
        );
        break;
    }

    return GestureDetector(
      child: card,
      onTap: () => _gotoSearchResult(paymentType: paymentType),
    );
  }

  /// Builds the card to display all detail types which is NOT included by analysis.
  Widget _buildNotIncludedDetailTypeCard() {
    return Card(
      color: Colors.grey[200],
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '以下类别未包含在分析数据内',
              style: TextStyle(color: Colors.black54),
            ),
            const SizedBox(height: 10.0),
            Text(
              '收入',
              style: Theme.of(
                context,
              ).textTheme.bodySmall!.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 2.0),
            Text(
              _analysisNotIncludedIncomeDetailTypes
                  .map((e) => e.title)
                  .join('，'),
              style: Theme.of(context).textTheme.bodySmall,
            ),
            const SizedBox(height: 10.0),
            Text(
              '支出',
              style: Theme.of(
                context,
              ).textTheme.bodySmall!.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 2.0),
            Text(
              _analysisNotIncludedExpenseDetailTypes
                  .map((e) => e.title)
                  .join('，'),
              style: Theme.of(context).textTheme.bodySmall,
            ),
            if (_currentUser?.isAccountGroupAdmin ?? false)
              Column(
                children: [
                  const SizedBox(height: 10.0),
                  InkWell(
                    child: Text(
                      '去修改',
                      style: Theme.of(
                        context,
                      ).textTheme.bodySmall!.copyWith(color: Colors.blue),
                    ),
                    onTap: () async {
                      final dataUpdated = await (NavigatorHelper.push<bool>(
                        context,
                        const IncomeExpenseDetailTypeHome(),
                      ));

                      // If detail type is changed then we need to reload the data.
                      if (dataUpdated == true) {
                        // We only need to set [_dataUpdated] as true when [dataUpdated] is true.
                        //
                        // - When user chaged one item then [dataUpdated] is true and [_dataUpdated] is true.
                        // - When user goes to edit view and gets back without any change, then [dataUpdated] is false.
                        // But we still need to notify last page that some items have been changed.
                        //
                        // That's why we cannnot set [_dataUpdated] to false when [dataUpdated] is false.
                        _dataUpdated = true;

                        // Changes this for reloading its data.
                        _analysisNotIncludedDetailTypesLoadingStatus =
                            LoadingStatus.notLoaded;

                        _markForDataReloading();
                        _prepareDataBasedOnTab();
                        _resetPagingListBasedOnTab();
                        _loadAnalysisNotIncludedDetailTypes();
                      }
                    },
                  ),
                ],
              ),
          ],
        ),
      ),
    );
  }

  /// Builds the bar chart for income/expense comparison.
  Widget _buildIncomeExpenseComparisonChart() {
    if (_comparisonDataLoadingStatus != LoadingStatus.loaded) {
      return _buildChartLoadingIndicator(Icons.insert_chart);
    }

    // return RefreshIndicator(
    //   child: _selectedMonth == null
    //       ? charts.BarChart(
    //           _incomeExpenseComparisonChartData,
    //           animate: true,
    //           vertical: false,
    //           barRendererDecorator: charts.BarLabelDecorator<String>(),
    //           barGroupingType: charts.BarGroupingType.grouped,
    //         )
    //       : ListView(children: [
    //           Container(
    //             height: 1400.0,
    //             child: charts.BarChart(
    //               _incomeExpenseComparisonChartData,
    //               animate: true,
    //               vertical: false,
    //               barRendererDecorator: charts.BarLabelDecorator<String>(),
    //               barGroupingType: charts.BarGroupingType.grouped,
    //             ),
    //           ),
    //         ]),
    //   onRefresh: () async {
    //     if (mounted) {
    //       setState(() {
    //         _comparisonDataLoadingStatus = LoadingStatus.notLoaded;
    //       });

    //       _prepareDataBasedOnTab();
    //     }
    //   },
    // );

    if (_selectedMonth == null) {
      return charts.BarChart(
        _incomeExpenseComparisonChartData
            as List<charts.Series<dynamic, String>>,
        animate: true,
        vertical: false,
        barRendererDecorator: charts.BarLabelDecorator<String>(),
        barGroupingType: charts.BarGroupingType.grouped,
      );
    } else {
      return ListView(
        children: [
          SizedBox(
            height: 1400.0,
            child: charts.BarChart(
              _incomeExpenseComparisonChartData
                  as List<charts.Series<dynamic, String>>,
              animate: true,
              vertical: false,
              barRendererDecorator: charts.BarLabelDecorator<String>(),
              barGroupingType: charts.BarGroupingType.grouped,
            ),
          ),
        ],
      );
    }
  }

  /// Builds the loading icon for chart.
  Widget _buildChartLoadingIndicator(IconData icon) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(icon, size: 60.0, color: Colors.grey[400]),
          const SizedBox(height: 10.0),
          Text('正在生成图表', style: TextStyle(color: Colors.grey[400])),
        ],
      ),
    );
  }

  // /// Builds the income and expense category charts
  // Widget _buildIncomeCategoryChart() {
  //   if (_incomeSummaryByDetailTypeDataLoadingStatus != LoadingStatus.loaded) {
  //     return _buildChartLoadingIndicator(Icons.pie_chart);
  //   }

  //   // The [TabBarView] behavior is:
  //   // - If we swipe the tab the UI will be rebuilt once. [_tabController.indexIsChanging] is false. Pie chart works.
  //   // - If we directly tap on the tab the UI will be rebuilt twice. First time [_tabController.indexIsChanging] is true. Second time [_tabController.indexIsChanging] is false. Pie chart does NOT show.
  //   // - If we tap on the tab but the process paused at breakpoint then we resume the execution. Pie chart works.
  //   // - If we tap on the tab but check on [_tabController.indexIsChanging]. When [_tabController.indexIsChanging] is false we start to set chart. But pie chart still does NOT work.
  //   // That's why we put [sleep] here to make pie chart work with both scenarios, tapping or swiping.
  //   sleep(Duration(milliseconds: 500));

  //   return _buildCategoryChart(_incomeSummaryByDetailTypeChartData, 110);
  // }

  // /// Builds the income and expense category charts
  // Widget _buildExpenseCategoryChart() {
  //   if (_expenseSummaryByDetailTypeDataLoadingStatus != LoadingStatus.loaded) {
  //     return _buildChartLoadingIndicator(Icons.pie_chart);
  //   }

  //   // The [TabBarView] behavior is:
  //   // - If we swipe the tab the UI will be rebuilt once. [_tabController.indexIsChanging] is false. Pie chart works.
  //   // - If we directly tap on the tab the UI will be rebuilt twice. First time [_tabController.indexIsChanging] is true. Second time [_tabController.indexIsChanging] is false. Pie chart does NOT show.
  //   // - If we tap on the tab but the process paused at breakpoint then we resume the execution. Pie chart works.
  //   // - If we tap on the tab but check on [_tabController.indexIsChanging]. When [_tabController.indexIsChanging] is false we start to set chart. But pie chart still does NOT work.
  //   // That's why we put [sleep] here to make pie chart work with both scenarios, tapping or swiping.
  //   sleep(Duration(milliseconds: 500));

  //   return _buildCategoryChart(_expenseSummaryByDetailTypeChartData, 110);
  // }

  // /// Builds the pie chart for income/expense comparison.
  // Widget _buildCategoryChart(
  //     List<charts.Series> categoryChartData, int arcWidth) {
  //   // If the data is empty then will return [Container] instead of [charts.PieChart].
  //   // The chart would through error when data is empty.
  //   if (categoryChartData.isEmpty) {
  //     return Container();
  //   } else {
  //     return charts.PieChart(
  //       categoryChartData,
  //       animate: true,
  //       defaultRenderer: charts.ArcRendererConfig(
  //         arcWidth: arcWidth,
  //         arcRendererDecorators: [
  //           charts.ArcLabelDecorator(),
  //         ],
  //       ),
  //     );
  //   }
  // }

  /// Builds the expense/income summary by detail type list order by amount based on privoded [incomeExpenseType].
  Widget _buildSummaryByDetailTypeList(String incomeExpenseType) {
    if ((incomeExpenseType == IncomeExpenseType.expense &&
            _expenseSummaryByDetailTypeDataLoadingStatus !=
                LoadingStatus.loaded) ||
        (incomeExpenseType == IncomeExpenseType.income &&
            _incomeSummaryByDetailTypeDataLoadingStatus !=
                LoadingStatus.loaded)) {
      return const MyPageTopCenterLoadingIndicator();
    }

    final data = incomeExpenseType == IncomeExpenseType.expense
        ? _expenseSummaryByDetailTypeData
        : _incomeSummaryByDetailTypeData;

    // Total amount for calculating pencentage.
    var totalAmount = 0.0;

    for (final item in data) {
      totalAmount += item['amount'];
    }

    return data.isEmpty
        ? const Padding(
            padding: EdgeInsets.only(top: 20.0),
            child: NoItemFound(),
          )
        : RefreshIndicator(
            child: MasonryGridView.count(
              crossAxisCount: 2,
              mainAxisSpacing: 4.0,
              crossAxisSpacing: 4.0,
              padding: const EdgeInsets.symmetric(vertical: (10.0)),
              itemCount: data.length,
              itemBuilder: (context, index) {
                final item = data[index];
                final amount = item['amount'];
                double? pencentage = amount / totalAmount * 100;

                // Calculates different height based on rate.
                double height = 110.0 + 300.0 * (amount / totalAmount);

                final detailType =
                    IncomeExpenseDetailTypeLoader.getIncomeExpenseDetailType(
                      incomeExpenseType,
                      item['detailType'],
                    )!;

                return GestureDetector(
                  child: Card(
                    elevation: 3.0,
                    child: Container(
                      height: height,
                      decoration: BoxDecoration(
                        border: Border(
                          left: BorderSide(
                            width: 2.0,
                            color: Theme.of(context).primaryColor,
                          ),
                        ),
                      ),
                      child: Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 10.0),
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          crossAxisAlignment: CrossAxisAlignment.center,
                          children: [
                            Text(
                              '${formatNumber(pencentage)}%',
                              style: Theme.of(context).textTheme.headlineMedium,
                            ),
                            const Divider(thickness: 1.0),
                            Text(
                              detailType.title!,
                              style: Theme.of(context).textTheme.titleSmall,
                            ),
                            const SizedBox(height: 6.0),
                            Text(
                              formatBalanceNumber(amount),
                              textAlign: TextAlign.right,
                              style: TextStyle(
                                color: IncomeExpenseDetailShared.getAmountColor(
                                  amount,
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                  onTap: () => _gotoSearchResult(
                    type: incomeExpenseType,
                    detailType: detailType.name,
                    paymentType: _paymentType,
                  ),
                );
              },
            ),
            onRefresh: () async {
              if (mounted) {
                setState(() {
                  if (incomeExpenseType == IncomeExpenseType.expense) {
                    _expenseSummaryByDetailTypeDataLoadingStatus =
                        LoadingStatus.notLoaded;
                  } else {
                    _incomeSummaryByDetailTypeDataLoadingStatus =
                        LoadingStatus.notLoaded;
                  }
                });
              }

              _prepareDataBasedOnTab();
            },
          );
  }

  /// Builds the top expense/income list order by amount based on privoded [incomeExpenseType].
  Widget _buildTopPagingList(String incomeExpenseType) {
    return PagingListView(
      padding: const EdgeInsets.only(top: 10.0),
      pageSize: _pageSize,
      getLoadController: (loadController) =>
          incomeExpenseType == IncomeExpenseType.expense
          ? _expenseTopListLoadController = loadController
          : _incomeTopListLoadController = loadController,
      retrieveData: (pageIndex) =>
          _incomeExpenseAnalysisService.queryTopRankByAccountGroup(
            _pageSize,
            pageIndex!,
            incomeExpenseType,
            _paymentType,
            widget.year,
            _selectedMonth,
          ),
      itemBuilder: (context, entry, index) => _buildDetail(entry),
    );
  }

  /// Builds the detail list tile for top paging list.
  Widget _buildDetail(IncomeExpenseDetail item) {
    return IncomeExpenseDetailListTile(item, () async {
      final dataUpdated = await NavigatorHelper.push<bool>(
        context,
        IncomeExpenseDetailView(item.id!),
      );

      // If the income/expense details is updated we need to refresh the list.
      if (dataUpdated == true) {
        // We only need to set [_dataUpdated] as true when [dataUpdated] is true.
        //
        // - When user chaged one item then [dataUpdated] is true and [_dataUpdated] is true.
        // - When user goes to edit view and gets back without any change, then [dataUpdated] is false.
        // But we still need to notify last page that some items have been changed.
        //
        // That's why we cannnot set [_dataUpdated] to false when [dataUpdated] is false.
        _dataUpdated = true;

        // For displaying loading icon.
        _markForDataReloading();

        // Prepares the data based on current tab.
        _prepareDataBasedOnTab();

        // Resets the top list.
        _resetPagingListBasedOnTab();
      }
    }, displayIncomeExpenseDate: true);
  }

  /// Shows a pick modal for month selection.
  void _showMonthModalBottomSheet(ValueChanged<int?> onTap, {int? month}) {
    const totalMonth = 12;

    showModalBottomSheet<void>(
      context: context,
      builder: (context) => Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const SizedBox(height: 20.0),
          Text('范围', style: Theme.of(context).textTheme.titleLarge),
          const SizedBox(height: 20.0),
          SizedBox(
            height: 230.0,
            child: ScrollablePositionedList.builder(
              itemCount: totalMonth,
              initialScrollIndex: month == null ? 0 : totalMonth - month,
              itemBuilder: (context, index) {
                final i = totalMonth - index;

                final item = ListTile(
                  selected: i == month,
                  title: Text('$i月'),
                  onTap: () => onTap(i),
                );

                return index == 0
                    ? Column(
                        children: [
                          ListTile(
                            selected: month == null,
                            title: const Text('全年'),
                            onTap: () => onTap(null),
                          ),
                          item,
                        ],
                      )
                    : item;
              },
            ),
          ),
        ],
      ),
    );
  }

  /// Listens on tab change event which might triggered by tapping or swiping.
  void _listenOnTabChange() {
    _setPaymentSelectionButtonVisibility();
    _prepareDataBasedOnTab();
  }

  /// Because preparing all data impacts UI performance. That's why we do it based on current tab.
  /// This method needs to be called when tab index or data retrieving related selection changes.
  void _prepareDataBasedOnTab() {
    if (_tabController.index == _overviewSummaryTabIndex &&
        _overviewSummaryLoadingStatus == LoadingStatus.notLoaded) {
      _loadAnalysisData();
    }

    if (_tabController.index == _comparisonChartTabIndex &&
        _comparisonDataLoadingStatus == LoadingStatus.notLoaded) {
      _loadAnalysisData(prepareComparisonChartData: true);
    }

    if (_tabController.index == _expenseSummaryByDetailTypeTabIndex &&
        _expenseSummaryByDetailTypeDataLoadingStatus ==
            LoadingStatus.notLoaded) {
      _loadSummaryByDetailTypeData(IncomeExpenseType.expense);
    }

    if (_tabController.index == _incomeSummaryByDetailTypeTabIndex &&
        _incomeSummaryByDetailTypeDataLoadingStatus ==
            LoadingStatus.notLoaded) {
      _loadSummaryByDetailTypeData(IncomeExpenseType.income);
    }
  }

  /// Resets the current top list.
  ///
  /// Cannot put the paging list reset to [_prepareDataBasedOnTab] because it will be called when tab changes.
  /// That would cause duplicate reset action.
  ///
  /// We only need to reset the current paging list. Because when we switch the tab the paging list will retrieve the data again.
  /// We don't have to manally reset the other paging list.
  void _resetPagingListBasedOnTab() {
    if (_tabController.index == _expenseTopListIndex) {
      _expenseTopListLoadController.reset();
    }

    if (_tabController.index == _incomeTopListIndex) {
      _incomeTopListLoadController.reset();
    }
  }

  /// Sets the payment selection button for selecting credit card/cash.
  void _setPaymentSelectionButtonVisibility() {
    if (_tabController.indexIsChanging && mounted) {
      setState(
        () => _showPaymentSelectionButton =
            _tabController.index != _overviewSummaryTabIndex,
      );
    }
  }

  Future<void> _getCurrentUser() async {
    final res = await AuthStateService.instance.currentUser;

    if (mounted) {
      setState(() => _currentUser = res);
    }
  }

  /// Loads the data for summary and chart.
  Future<void> _loadAnalysisData({
    bool prepareComparisonChartData = false,
  }) async {
    _overviewSummaryLoadingStatus = LoadingStatus.loading;

    if (prepareComparisonChartData) {
      _comparisonDataLoadingStatus = LoadingStatus.loading;
    }

    if (widget.year == null) {
      /// Loads overall data for income/expense comparison chart.
      _summaryDataCollection = await _incomeExpenseAnalysisService
          .queryOverallSummary();
    } else {
      if (_selectedMonth == null) {
        /// Loads annual data for income/expense comparison chart.
        _summaryDataCollection = await _incomeExpenseAnalysisService
            .queryAnnualMonthlySummary(widget.year);
      } else {
        /// Loads monthly/daily data for income/expense comparison chart.
        _summaryDataCollection = await _incomeExpenseAnalysisService
            .queryMonthlyDailySummary(widget.year, _selectedMonth);
      }
    }

    if (mounted) {
      setState(
        () => _prepareOverviewSummaryData(
          prepareComparisonChartData: prepareComparisonChartData,
        ),
      );
    }
  }

  /// Loads all analysis not included detail types for user.
  Future<void> _loadAnalysisNotIncludedDetailTypes() async {
    final res = await _incomeExpenseDetailTypeService
        .queryAnalysisNotIncludedDetailTypes();

    if (mounted) {
      setState(() {
        _analysisNotIncludedIncomeDetailTypes = res['incomeDetailTypes']!;
        _analysisNotIncludedExpenseDetailTypes = res['expenseDetailTypes']!;
        _analysisNotIncludedDetailTypesLoadingStatus = LoadingStatus.loaded;
      });
    }
  }

  /// Loads income/expense summary by detail type data.
  Future<void> _loadSummaryByDetailTypeData(String type) async {
    if (type == IncomeExpenseType.expense) {
      _expenseSummaryByDetailTypeDataLoadingStatus = LoadingStatus.loading;
    } else {
      _incomeSummaryByDetailTypeDataLoadingStatus = LoadingStatus.loading;
    }

    final res = await _incomeExpenseAnalysisService.querySummaryByDetailType(
      widget.year,
      _selectedMonth,
      type,
      _paymentType,
    );

    if (mounted) {
      setState(() {
        if (type == IncomeExpenseType.expense) {
          // _expenseSummaryByDetailTypeChartData =
          //     _generateCategoryChartData(res, IncomeExpenseType.expense);

          _expenseSummaryByDetailTypeData = res;
          _expenseSummaryByDetailTypeDataLoadingStatus = LoadingStatus.loaded;
        } else {
          // _incomeSummaryByDetailTypeChartData =
          //     _generateCategoryChartData(res, IncomeExpenseType.income);

          _incomeSummaryByDetailTypeData = res;
          _incomeSummaryByDetailTypeDataLoadingStatus = LoadingStatus.loaded;
        }
      });
    }
  }

  /// Marks the status for next reloading based on current is [markAll] is false.
  void _markForDataReloading({bool markAll = false}) {
    if (mounted) {
      setState(() {
        _overviewSummaryLoadingStatus = LoadingStatus.notLoaded;
        _comparisonDataLoadingStatus = LoadingStatus.notLoaded;

        // We need to reload expense related data when:
        // - User changes analysis not included detail types in overview summary tab.
        // - User changes income/expense detail in expense summary by detail type tab.
        // - User changes income/expense detail in expense top list tab.
        if (markAll ||
            _tabController.index == _overviewSummaryTabIndex ||
            _tabController.index == _expenseSummaryByDetailTypeTabIndex ||
            _tabController.index == _expenseTopListIndex) {
          _expenseSummaryByDetailTypeDataLoadingStatus =
              LoadingStatus.notLoaded;
        }

        // We need to reload income related data when:
        // - User changes analysis not included detail types in overview summary tab.
        // - User changes income/expense detail in income summary by detail type tab.
        // - User changes income/expense detail in income top list tab.
        if (markAll ||
            _tabController.index == _overviewSummaryTabIndex ||
            _tabController.index == _incomeSummaryByDetailTypeTabIndex ||
            _tabController.index == _incomeTopListIndex) {
          _incomeSummaryByDetailTypeDataLoadingStatus = LoadingStatus.notLoaded;
        }
      });
    }
  }

  /// Prepares the data for overview summary data.
  void _prepareOverviewSummaryData({bool prepareComparisonChartData = false}) {
    if (widget.year == null) {
      _annualCashIncomeAmount = double.parse(
        _summaryDataCollection!['cashIncomeSummary']['total'].toString(),
      );

      _annualCashExpenseAmount = double.parse(
        _summaryDataCollection!['cashExpenseSummary']['total'].toString(),
      );

      _annualCreditCardIncomeAmount = double.parse(
        _summaryDataCollection!['creditCardIncomeSummary']['total'].toString(),
      );

      _annualCreditCardExpenseAmount = double.parse(
        _summaryDataCollection!['creditCardExpenseSummary']['total'].toString(),
      );
    } else {
      // Annual summary displayed in overview tab.
      final summaryDataKey = _selectedMonth == null
          ? '${widget.year}'
          : '${widget.year}$_selectedMonth';

      _annualCashIncomeAmount = double.parse(
        _summaryDataCollection!['cashIncomeSummary'][summaryDataKey].toString(),
      );

      _annualCashExpenseAmount = double.parse(
        _summaryDataCollection!['cashExpenseSummary'][summaryDataKey]
            .toString(),
      );

      _annualCreditCardIncomeAmount = double.parse(
        _summaryDataCollection!['creditCardIncomeSummary'][summaryDataKey]
            .toString(),
      );

      _annualCreditCardExpenseAmount = double.parse(
        _summaryDataCollection!['creditCardExpenseSummary'][summaryDataKey]
            .toString(),
      );
    }

    _overviewSummaryLoadingStatus = LoadingStatus.loaded;

    if (prepareComparisonChartData) {
      _prepareComparisonChartData();
    }
  }

  /// Prepares the data for comparison chart.
  void _prepareComparisonChartData() {
    // For income/expense comparison chart.
    if (_paymentType == PaymentType.cash) {
      _incomeExpenseComparisonChartData =
          _generateIncomeExpenseComparisonChartData(
            _summaryDataCollection!['cashIncomeSummary'],
            _summaryDataCollection!['cashExpenseSummary'],
          );
    } else if (_paymentType == PaymentType.creditCard) {
      _incomeExpenseComparisonChartData =
          _generateIncomeExpenseComparisonChartData(
            _summaryDataCollection!['creditCardIncomeSummary'],
            _summaryDataCollection!['creditCardExpenseSummary'],
          );
    } else {
      final totalIncomeSummary = _calculateTotalSummary(
        _summaryDataCollection!['cashIncomeSummary'],
        _summaryDataCollection!['creditCardIncomeSummary'],
      );

      final totalExpenseSummary = _calculateTotalSummary(
        _summaryDataCollection!['cashExpenseSummary'],
        _summaryDataCollection!['creditCardExpenseSummary'],
      );

      _incomeExpenseComparisonChartData =
          _generateIncomeExpenseComparisonChartData(
            totalIncomeSummary,
            totalExpenseSummary,
          );
    }

    _comparisonDataLoadingStatus = LoadingStatus.loaded;
  }

  /// Generates series list with multiple series for income/expense comparison.
  List<charts.Series<AmountStatistic, String>>
  _generateIncomeExpenseComparisonChartData(
    Map<String, dynamic> incomeSummary,
    Map<String, dynamic> expenseSummary,
  ) {
    // Prepares the data for cash income.
    final incomeStatisticalData = <AmountStatistic>[];

    incomeSummary.forEach((key, value) {
      if (widget.year == null) {
        // Only takes the value belongs to year, skips 'total' data.
        if (int.tryParse(key) != null) {
          incomeStatisticalData.add(
            AmountStatistic(key, double.parse(value.toString())),
          );
        }
      } else {
        // Only takes the value belongs to month. Skips the year value.
        if (int.parse(key) <= 31) {
          incomeStatisticalData.add(
            AmountStatistic(
              '$key${_selectedMonth == null ? "月" : ""}',
              double.parse(value.toString()),
            ),
          );
        }
      }
    });

    incomeStatisticalData.sort((a, b) => b.label.compareTo(a.label));

    // Prepares the data for cash expense.
    final expenseStatisticalData = <AmountStatistic>[];

    // Takes the data absolute value for chart.
    // Because the expense stores in data source as negative value like -100.
    expenseSummary.forEach((key, value) {
      if (widget.year == null) {
        // Only takes the value belongs to year, skips 'total' data.
        if (int.tryParse(key) != null) {
          expenseStatisticalData.add(
            AmountStatistic(key, double.parse(value.abs().toString())),
          );
        }
      } else {
        // Only takes the value belongs to month. Skips the year value.
        if (int.parse(key) <= 31) {
          expenseStatisticalData.add(
            AmountStatistic(
              '$key${_selectedMonth == null ? "月" : ""}',
              double.parse(value.abs().toString()),
            ),
          );
        }
      }
    });

    expenseStatisticalData.sort((a, b) => b.label.compareTo(a.label));

    return [
      charts.Series<AmountStatistic, String>(
        id: 'incomeChart',
        domainFn: (summary, _) => summary.label,
        measureFn: (summary, _) => summary.amount,
        data: incomeStatisticalData,
        labelAccessorFn: (summary, _) => '收入 ${formatNumber(summary.amount)}',
        insideLabelStyleAccessorFn: (summary, _) =>
            const charts.TextStyleSpec(color: charts.Color.white),
        outsideLabelStyleAccessorFn: (summary, _) => charts.TextStyleSpec(
          color: charts.MaterialPalette.blue.shadeDefault,
        ),
      ),
      charts.Series<AmountStatistic, String>(
        id: 'expenseChart',
        domainFn: (summary, _) => summary.label,
        measureFn: (summary, _) => summary.amount,
        data: expenseStatisticalData,
        labelAccessorFn: (summary, _) => '支出 ${formatNumber(summary.amount)}',
        insideLabelStyleAccessorFn: (summary, _) =>
            const charts.TextStyleSpec(color: charts.Color.white),
        outsideLabelStyleAccessorFn: (summary, _) => charts.TextStyleSpec(
          color: charts.MaterialPalette.blue.shadeDefault,
        ),
      ),
    ];
  }

  /// Goes to [IncomeExpenseSearchResult] widget with current search criteria.
  Future<void> _gotoSearchResult({
    String? type,
    String? detailType,
    String? paymentType,
  }) async {
    final searchArguments = IncomeExpenseSearchArguments(
      type: type,
      detailType: detailType,
      paymentType: paymentType,
    );

    if (widget.year != null) {
      // If the month is null means need to search the entire year data.
      // The start date would be like 2020-1-1.
      // The end date would be like 2020-12-31.

      // If the month is NOT null means need to search the data within this month.
      // If the month is 3 then:
      // The start date would be like 2020-3-1.
      // The end date would be like 2020-3-31.
      searchArguments.startDate = getFirstDay(widget.year!, _selectedMonth);

      searchArguments.endDate = getLastDay(widget.year!, _selectedMonth ?? 12);
    }

    final dataUpdated = await NavigatorHelper.push<bool>(
      context,
      IncomeExpenseSearchResult(searchArguments: searchArguments),
    );

    if (dataUpdated == true) {
      // We only need to set [_dataUpdated] as true when [dataUpdated] is true.
      //
      // - When user chaged one item then [dataUpdated] is true and [_dataUpdated] is true.
      // - When user goes to edit view and gets back without any change, then [dataUpdated] is false.
      // But we still need to notify last page that some items have been changed.
      //
      // That's why we cannnot set [_dataUpdated] to false when [dataUpdated] is false.
      _dataUpdated = true;

      // For displaying loading icon.
      _markForDataReloading();

      // Prepares the data based on current tab.
      _prepareDataBasedOnTab();
    }
  }

  void _onPopInvokedWithResult(bool didPop, dynamic result) {
    if (didPop) {
      return;
    }

    NavigatorHelper.pop(context, _dataUpdated);
  }

  // /// Generates series list income category.
  // List<charts.Series<AmountStatistic, String>> _generateCategoryChartData(
  //     Map<String, dynamic> detailTypeSummary, String type) {
  //   // Pie chart does not support value 0.
  //   // That's why we need to remove all items with value 0.
  //   detailTypeSummary.removeWhere((key, value) => value == 0);

  //   // If the all items has been removed then returns an empty result to prevent chart error.
  //   if (detailTypeSummary.isEmpty) {
  //     return [];
  //   }

  //   // Prepares the data for chart.
  //   final incomeStatisticalData = <AmountStatistic>[];

  //   detailTypeSummary.forEach((key, value) {
  //     final label =
  //         IncomeExpenseDetailTypeLoader.getIncomeExpenseDetailType(type, key)
  //             .title;

  //     incomeStatisticalData.add(AmountStatistic(label, value));
  //   });

  //   return [
  //     charts.Series<AmountStatistic, String>(
  //       id: type == IncomeExpenseType.income
  //           ? 'incomeCategoryChart'
  //           : 'expenseCategoryChart',
  //       domainFn: (summary, _) => summary.label,
  //       measureFn: (summary, _) => summary.amount,
  //       data: incomeStatisticalData,
  //       labelAccessorFn: (AmountStatistic row, _) =>
  //           '${row.label}\n${formatNumber(row.amount)}',
  //     )
  //   ];
  // }
}
